package org.funtester.performance.books.chapter04.section2;

import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.MessageConstraints;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;

public class HttpClientTool {

    /**
     * 默认字符集,用于请求体和响应体的解析
     */
    public static Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    /**
     * 最大连接数,连接池最大连接数,单位个
     */
    public static int MAX_TOTAL_CONNECTION = 8000;

    /**
     * 每个路由最大连接数,路由是对maxTotal的细分,比如:一个域名对应一个路由,一个ip对应一个路由
     */
    public static int MAX_PER_ROUTE_CONNECTION = 1000;

    /**
     * 最大请求头数量,单位个
     */
    public static int MAX_HEADER_COUNT = 100;

    /**
     * 最大请求行长度,单位字符
     */
    public static int MAX_LINE_LENGTH = 10000;

    /**
     * 连接请求超时时间,从连接池获取连接的超时时间,超过该时间未获取到可用连接,则抛出异常,单位毫秒
     */
    public static int CONNECT_REQUEST_TIMEOUT = 3000;

    /**
     * 连接超时时间,连接建立时间,三次握手完成时间,单位毫秒
     */
    public static int CONNECT_TIMEOUT = 3000;

    /**
     * 套接字超时时间,请求数据传输过程中数据包之间间隔的最大时间,单位毫秒
     */
    public static int SOCKET_TIMEOUT = 3000;

    /**
     * 最大重试次数,默认3次
     */
    public static int MAX_TRY_TIMES = 3;

    /**
     * SSL版本,默认TLSv1.2
     */
    public static String SSL_VERSION = "TLSv1.2";

    /**
     * 连接池中连接最大空闲时间,单位秒
     */
    public static int IDLE_TIMEOUT = 60;

    /**
     * 获取默认的httpClient对象
     *
     * @return
     */
    public static CloseableHttpClient getHttpClient() {
        return HttpClients.custom().setConnectionManager(getPoolingHttpClientConnectionManager()).setRetryHandler(getHttpRequestRetryHandler()).setDefaultRequestConfig(getRequestConfig()).build();
    }


    /**
     * 获取连接池管理对象
     * @return
     */
    private static PoolingHttpClientConnectionManager getPoolingHttpClientConnectionManager() {
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()// 创建套接字注册器
                .register("http", PlainConnectionSocketFactory.INSTANCE)// 注册http套接字工厂
                .register("https", new SSLConnectionSocketFactory(createIgnoreVerifySSL(), NoopHostnameVerifier.INSTANCE))// 注册https套接字工厂
                .build();// 创建套接字注册器
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, getDnsResolver());// 创建连接池管理器
        MessageConstraints messageConstraints = MessageConstraints.custom().setMaxHeaderCount(MAX_HEADER_COUNT).setMaxLineLength(MAX_LINE_LENGTH).build();
        ConnectionConfig connectionConfig = ConnectionConfig.custom().setMalformedInputAction(CodingErrorAction.IGNORE).setUnmappableInputAction(CodingErrorAction.IGNORE).setCharset(DEFAULT_CHARSET).setMessageConstraints(messageConstraints).build();// 创建连接配置
        connManager.setDefaultConnectionConfig(connectionConfig);// 设置默认连接配置
        connManager.setMaxTotal(MAX_TOTAL_CONNECTION);// 设置最大连接数
        connManager.setDefaultMaxPerRoute(MAX_PER_ROUTE_CONNECTION);// 设置每个路由最大连接数
        return connManager;
    }

    /**
     * 获取SSL套接字对象 重点重点：设置tls协议的版本
     *
     * @return
     */
    private static SSLContext createIgnoreVerifySSL() {
        SSLContext sslContext = null;// 创建套接字对象
        try {
            sslContext = SSLContext.getInstance(SSL_VERSION);// 指定TLS版本
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        // 实现X509TrustManager接口，用于绕过验证
        X509TrustManager trustManager = new X509TrustManager() {
            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                                           String paramString) {
            }
            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                                           String paramString) {
            }
            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        try {
            sslContext.init(null, new TrustManager[]{trustManager}, null);// 初始化sslContext对象
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return sslContext;
    }

    /**
     * 重写Java自定义DNS解析器,可用于负载均衡设置
     *
     * @return
     */
    private static DnsResolver getDnsResolver() {
        return new SystemDefaultDnsResolver() {
            @Override
            public InetAddress[] resolve(final String host) throws UnknownHostException {
                if (host.equalsIgnoreCase("fun.tester")) {
                    return new InetAddress[]{InetAddress.getByName("127.0.0.1")};
                } else {
                    return super.resolve(host);
                }
            }
        };
    }

    /**
     * 获取重试处理器
     *
     * @return
     */
    private static HttpRequestRetryHandler getHttpRequestRetryHandler() {
        return new HttpRequestRetryHandler() {
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                boolean log = log(exception, executionCount, context);// 记录重试信息,判断是否重试
                return log;
            }

            /** 记录重试信息,避免错误日志影响观感
             * @param exception
             * @param executionCount
             * @param context
             * @return
             */
            private boolean log(IOException exception, int executionCount, HttpContext context) {
                if (executionCount >= MAX_TRY_TIMES) return false;// 如果已经重试了MAX_TRY_TIMES次，就放弃
                System.out.println("重试 " + executionCount + " , 错误信息: " + exception.getMessage());// 记录重试次数和错误信息
                HttpClientContext clientContext = HttpClientContext.adapt(context);// 获取请求上下文
                final Object request = clientContext.getAttribute(HttpCoreContext.HTTP_REQUEST);// 获取请求对象
                if (request instanceof HttpRequestBase) {// 如果请求对象是HttpRequestBase类型
                    HttpRequestBase uriRequest = (HttpRequestBase) request;// 强转为HttpRequestBase类型对象
                    System.out.println("请求失败接口URI: " + uriRequest.getURI().toString());// 获取请求URI
                }
                if (exception instanceof NoHttpResponseException) {// 如果是服务器丢掉了连接，那么就重试
                    return true;
                } else if (exception instanceof InterruptedIOException) {// 超时,重试
                    return true;
                } else if (exception instanceof UnknownHostException) {// 目标服务器不可达,不重试
                    return false;
                } else if (exception instanceof SSLException) {// SSL握手异常,不重试
                    return false;
                } else if (exception instanceof SocketException) {// Socket连接异常,不重试
                    return false;
                } else {
                    System.out.println("未记录的请求异常: " + exception.getClass().getName());// 记录未记录的异常
                }
                return true;// 重试
            }
        };
    }

    /**
     * 获取请求配置
     *
     * @return
     */
    public static RequestConfig getRequestConfig() {
        return RequestConfig.custom()
                .setConnectionRequestTimeout(CONNECT_REQUEST_TIMEOUT)
                .setConnectTimeout(CONNECT_TIMEOUT)
                .setSocketTimeout(SOCKET_TIMEOUT)
                .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
                .setRedirectsEnabled(false)
                .build();
    }


    /**
     * 回收资源方法，关闭过期连接，关闭超时连接，用于异步起线程回收连接池连接
     */
    public static void recyclingConnection(CloseableHttpClient closeableHttpsClients) {
        ClientConnectionManager connManager = closeableHttpsClients.getConnectionManager();
        connManager.closeExpiredConnections();// 关闭过期连接
        connManager.closeIdleConnections(IDLE_TIMEOUT, TimeUnit.SECONDS);// 关闭空闲连接
    }


}
